/*
Copyright SecureKey Technologies Inc. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/
package ichain_sdk

import (
	"sync"
	"sync/atomic"
	"time"

	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/core/config"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/providers/core"
)

const (
	internal = 30 // sdk个数检测周期
	ttl      = 60 // sdk缓存生存时间(s)
)

const (
	statusInit    = 0
	statusRunning = 1
	statusClosing = 2
	statusClosed  = 3
)

var (
	once  = sync.Once{}
	cache *sdkCache
)

// sdkCacheParams sdk缓存池参数
type sdkCacheParams struct {
	key string // 缓存的唯一标识
	ts  int64  // 记录缓存的时间，进行比对
}

// sdkCache sdk的缓存池
type sdkCache struct {
	lock     sync.RWMutex
	allSdk   map[string]*wrapperSDK
	status   int32
	closeCh  chan struct{}
	internal int
	ttl      int64
}

type wrapperSDK struct {
	sdk      *IChainSDK
	deadline int64
	ref      int32
}

func (ws *wrapperSDK) refCount() int32 {
	return atomic.LoadInt32(&ws.ref)
}

func (ws *wrapperSDK) addRef() {
	newRef := atomic.AddInt32(&ws.ref, 1)
	if newRef <= 0 {
		atomic.CompareAndSwapInt32(&ws.ref, newRef, 1)
	}
}

func (ws *wrapperSDK) closeRef() {
	newRef := atomic.AddInt32(&ws.ref, -1)
	if newRef < 0 {
		atomic.CompareAndSwapInt32(&ws.ref, newRef, 0)
	}
}

func (ws *wrapperSDK) borrow() *IChainSDK {
	ws.addRef()
	return ws.sdk
}

func (ws *wrapperSDK) release() {
	ws.closeRef()
}

func (ws *wrapperSDK) isFree() bool {
	return ws.refCount() <= 0
}

func getSdkCache() *sdkCache {
	once.Do(func() {
		cache = &sdkCache{
			allSdk:   make(map[string]*wrapperSDK),
			closeCh:  make(chan struct{}),
			internal: internal,
			ttl:      ttl,
		}
	})
	return cache
}

// SetFreshInternal 设置刷新时间，单位秒；只有未开启缓存的时候可以设置
// @param internal
func (c *sdkCache) SetFreshInternal(internal int) {
	if !c.isInit() {
		return
	}
	c.internal = internal
}

// SetTTL 设置缓存生存时间，单位秒；只有未开启缓存的时候可以设置
// @param ttl
func (c *sdkCache) SetTTL(ttl int64) {
	if !c.isInit() {
		return
	}
	c.ttl = ttl
}

// Start 开启sdk缓存池
func (c *sdkCache) Start() {
	if !c.setRunning() {
		logger.Warnf("change cache status fail now status is %s", c.status)
		return
	}
	go func() {
		checkTicker := time.NewTicker(time.Duration(c.internal) * time.Second)
		for {
			select {
			case <-checkTicker.C:
				c.timerToCheck()
			case <-c.closeCh:
				return
			}
		}
	}()
	logger.Infof("start sdk cache ok!")
}

// releaseSDK 放回sdk缓存
// 1: 没有放过缓存的sdk，直接close
// 2: 在缓存中获取的sdk，ref=ref-1
// @param sdk
// @return bool
func (c *sdkCache) releaseSDK(sdk *IChainSDK) {
	logger.Debugf("begin release sdk %s %s", sdk.key, sdk.ts)
	// 不在缓存内的直接关闭
	if sdk.sdkCacheParams.key == "" || sdk.ts == 0 {
		logger.Debugf("sdk not in cache, just close it!")
		sdk.closeSDK()
		return
	}
	// 如果未开启cache，则直接关闭
	if c.isInit() {
		logger.Debugf("cache not running, just close it!")
		sdk.closeSDK()
		return
	}
	// 在缓存内的查看是否还在缓存内
	c.lock.RLock()
	defer c.lock.RUnlock()
	wrapSdk, ok := c.allSdk[sdk.sdkCacheParams.key]
	if !ok || wrapSdk.sdk.ts != sdk.ts {
		logger.Debugf("sdk not same in cache, just close it !")
		sdk.closeSDK()
		return
	}
	logger.Debugf("release ok!")
	wrapSdk.release()
	logger.Debugf("%s sdk now ref is %d", sdk.key, wrapSdk.refCount())
}

// get 获取一个sdk
// @param cfgProvider
// @return *IChainSDK
// @return bool
func (c *sdkCache) get(cfgProvider core.ConfigProvider, opts ...Option) (*IChainSDK, error) {
	logger.Debugf("begin get %s sdk from cache!", cfgProvider.Id)
	if !c.isRunning() {
		logger.Debugf("cache is not running, cannot get sdk from cache")
		return newSDK(cfgProvider, opts...)
	}

	// 无效的id，直接返回一个新的
	if cfgProvider.Id == config.InvalidId || len(opts) > 0 {
		logger.Warnf("the id is nil or has opts, cannot get sdk from cache!")
		return newSDK(cfgProvider, opts...)
	}

	var (
		sdk *wrapperSDK
		ok  bool
	)
	// 在缓存里获取，如果成功了
	c.lock.RLock()
	sdk, ok = c.allSdk[cfgProvider.Id]
	c.lock.RUnlock()
	if ok {
		logger.Debugf("get sdk from cache ok!")
		return sdk.borrow(), nil
	}

	c.lock.Lock()
	defer c.lock.Unlock()

	// 再次尝试读取sdk
	sdk, ok = c.allSdk[cfgProvider.Id]
	if ok {
		logger.Debugf("second times to get sdk from cache ok!")
		return sdk.borrow(), nil
	}
	createSDK, err := newSDK(cfgProvider)
	if err != nil {
		return nil, err
	}
	// 标记是在cache中生成的sdk
	createSDK.sdkCacheParams.key = cfgProvider.Id
	createSDK.sdkCacheParams.ts = time.Now().Unix()

	sdk = &wrapperSDK{
		sdk:      createSDK,
		deadline: time.Now().Unix() + c.ttl,
		ref:      1,
	}
	c.allSdk[cfgProvider.Id] = sdk
	logger.Debugf("create sdk into cache ok!")
	return createSDK, nil
}

// timerToCheck 检测是否有需要清理的缓存
func (c *sdkCache) timerToCheck() {
	c.lock.Lock()
	defer c.lock.Unlock()
	now := time.Now().Unix()
	update := false
	for key, wrapSdk := range c.allSdk {
		if !wrapSdk.isFree() {
			continue
		}
		if now < wrapSdk.deadline {
			continue
		}
		update = true
		logger.Debugf("begin clean the overtime cache %s, the deadline is %d", wrapSdk.sdk.key, wrapSdk.deadline)
		delete(c.allSdk, key)
		wrapSdk.sdk.closeSDK()
	}
	if update {
		logger.Debugf("the rest number of sdk in cache is %d", len(c.allSdk))
	}
}

// Close 退出cache
func (c *sdkCache) Close() {
	logger.Infof("begin quit the cache")
	if !c.setClosing() {
		logger.Infof("repeat quit cache!")
		return
	}
	c.lock.Lock()
	defer c.lock.Unlock()
	for _, sdk := range c.allSdk {
		sdk.sdk.closeSDK()
	}
	c.setClosed()
	close(c.closeCh)
}

func (c *sdkCache) isClosed() bool {
	return atomic.LoadInt32(&c.status) >= statusClosing
}

func (c *sdkCache) isInit() bool {
	return atomic.LoadInt32(&c.status) == statusInit
}

func (c *sdkCache) isRunning() bool {
	return atomic.LoadInt32(&c.status) == statusRunning
}

func (c *sdkCache) setClosing() bool {
	return atomic.CompareAndSwapInt32(&c.status, statusRunning, statusClosing)
}

func (c *sdkCache) setClosed() bool {
	return atomic.CompareAndSwapInt32(&c.status, statusClosing, statusClosed)
}

func (c *sdkCache) setRunning() bool {
	return atomic.CompareAndSwapInt32(&c.status, statusInit, statusRunning)
}
